import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;

public class Main {
    static class Flow{
        int id;
        int bandwidth;

        int startTime;
        int spendTime;
        public Flow(int id, int bandwidth, int startTime, int spendTime){
            this.id = id;
            this.bandwidth = bandwidth;
            this.startTime = startTime;
            this.spendTime = spendTime;
        }

        public Flow() {

        }

        @Override
        public String toString() {
            return "Flow{" +
                    "id=" + id +
                    ", bandwidth=" + bandwidth +
                    ", startTime=" + startTime +
                    ", spendTime=" + spendTime +
                    '}';
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public int getBandwidth() {
            return bandwidth;
        }

        public void setBandwidth(int bandwidth) {
            this.bandwidth = bandwidth;
        }

        public int getStartTime() {
            return startTime;
        }

        public void setStartTime(int startTime) {
            this.startTime = startTime;
        }

        public int getSpendTime() {
            return spendTime;
        }

        public void setSpendTime(int spendTime) {
            this.spendTime = spendTime;
        }
    }
    static class Port{
        int id;
        int bandwidth;

        int remainingBandwidth;

        public Port(int id, int bandwidth){
            this.id = id;
            this.bandwidth = bandwidth;
            this.remainingBandwidth = bandwidth;
        }

        public Port() {

        }

        @Override
        public String toString() {
            return "Port{" +
                    "id=" + id +
                    ", bandwidth=" + bandwidth +
                    ", remainingBandwidth=" + remainingBandwidth +
                    '}';
        }

        public int getRemainingBandwidth() {
            return remainingBandwidth;
        }

        public void setRemainingBandwidth(int remainingBandwidth) {
            this.remainingBandwidth = remainingBandwidth;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public int getBandwidth() {
            return bandwidth;
        }

        public void setBandwidth(int bandwidth) {
            this.bandwidth = bandwidth;
        }
    }

    static class Schedule{
        Flow flow;
        Port port;
        int startTime;
        int endTime;

        public Schedule(Flow flow, Port port, int startTime, int endTime) {
            this.flow = flow;
            this.port = port;
            this.startTime = startTime;
            this.endTime = endTime;
        }

        @Override
        public String toString() {
            return "Schedule{" +
                    "flow=" + flow +
                    ", port=" + port +
                    ", startTime=" + startTime +
                    ", endTime=" + endTime +
                    '}';
        }

        public Flow getFlow() {
            return flow;
        }

        public void setFlow(Flow flow) {
            this.flow = flow;
        }

        public Port getPort() {
            return port;
        }

        public void setPort(Port port) {
            this.port = port;
        }

        public int getStartTime() {
            return startTime;
        }

        public void setStartTime(int startTime) {
            this.startTime = startTime;
        }

        public int getEndTime() {
            return endTime;
        }

        public void setEndTime(int endTime) {
            this.endTime = endTime;
        }
    }
    static List<Flow> getFlowList(String filePath) throws IOException {
        List<Flow> flowList = new ArrayList<Flow>();
        String fileName = filePath+"/flow.txt";
        BufferedReader in = new BufferedReader(new FileReader(fileName));
        String str;
        in.readLine();
        while ((str = in.readLine()) != null) {
            String[] strings = str.split(",");
            Flow flow = new Flow(Integer.valueOf(strings[0]), Integer.valueOf(strings[1]), Integer.valueOf(strings[2]), Integer.valueOf(strings[3]) );
            flowList.add(flow);
        }

        return  flowList;
    }

    static List<Port> getPortList(String filePath) throws IOException {
        List<Port> portList = new ArrayList<Port>();
        String fileName = filePath+"/port.txt";

        BufferedReader in = new BufferedReader(new FileReader(fileName));
        String str;
        in.readLine();
        while ((str = in.readLine()) != null) {
            String[] strings = str.split(",");
            Port port = new Port(Integer.valueOf(strings[0]), Integer.valueOf(strings[1]));
            portList.add(port);
        }

        return  portList;
    }

    static List<Flow> flowList;
    static List<Port> portList;
    static LinkedList<Schedule> schedulesList = new LinkedList<>();


    public static void main(String[] args) throws IOException {
         String dataFilePath = "../data";
         File dataFiles = new File(dataFilePath);
        for (String dataFile : dataFiles.list()) {
            String filePath = dataFilePath +"/"+ dataFile;
            method(filePath);
        }
    }

    static void method(String filePath) throws IOException {
        // 读取文件，获取流信息 和  端口信息
        flowList = getFlowList(filePath);
        portList = getPortList(filePath);
        Queue<Flow> optionalFlows = new PriorityQueue<Flow>(((o1, o2) -> {
            if(o1.getSpendTime() == o2.getSpendTime()){
                return o2.getBandwidth() - o1.getBandwidth();
            }
            return o2.getSpendTime() - o1.getSpendTime();
        }));
        String fileName =filePath+ "/result.txt";;
        File file = new File(fileName);
        if(file.exists()){
            file.delete();
        }
        file.createNewFile();
        Path path = Paths.get(fileName);
        //追加写模式
        BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
        // 将 流 按照到达时间从早到晚排序    花费时间从多到少   带宽小到大
        Collections.sort(flowList, (o1, o2) -> {
            if(o1.getStartTime() == o2.getStartTime()){
                if(o1.getSpendTime() == o2.getSpendTime()){
                    return o1.getBandwidth() - o2.getBandwidth();
                }
                return -o1.getSpendTime() +o2.getSpendTime();
            }
            return o1.getStartTime() - o2.getStartTime();
        });

        int time = 0;
        boolean hasFinishedSchedule = false;
        boolean hasNewFlow = false;
        for (int i = 0; i < flowList.size();i++) {
            //  查看是否有任务 完成  如果有  移除该任务， 并且恢复端口的 剩余带宽， 并将该任务加入完成任务队列
            hasFinishedSchedule = false;
            while(schedulesList.size() > 0 && schedulesList.getFirst().getEndTime() <= time){
                Schedule firstSchedules = schedulesList.getFirst();
                Port port = firstSchedules.getPort();
                Flow flow = firstSchedules.getFlow();
                port.setRemainingBandwidth( port.getRemainingBandwidth() + flow.getBandwidth());
                schedulesList.removeFirst();
                hasFinishedSchedule = true;
            }
            hasNewFlow = false;
            while( i<flowList.size() && flowList.get(i).getStartTime() <= time){
                Flow flow = flowList.get(i);
                optionalFlows.add(flow);
                i++;
                hasFinishedSchedule = true;
            }
            i--;
            if(hasFinishedSchedule || hasNewFlow){
                chooseFlowAndPort(optionalFlows,time,writer);
            }

            time++;

        }

        while (optionalFlows.size()>0){
            //  查看是否有任务 完成  如果有  移除该任务， 并且恢复端口的 剩余带宽， 并将该任务加入完成任务队列
            hasFinishedSchedule = false;
            while(schedulesList.size() > 0 && schedulesList.getFirst().getEndTime() <= time){
                Schedule firstSchedules = schedulesList.getFirst();
                Port port = firstSchedules.getPort();
                Flow flow = firstSchedules.getFlow();
                port.setRemainingBandwidth( port.getRemainingBandwidth() + flow.getBandwidth());
                schedulesList.removeFirst();
                hasFinishedSchedule = true;
            }
            if( hasFinishedSchedule){
                chooseFlowAndPort(optionalFlows,time,writer);
            }
            time++;
        }
        writer.close();
        while (schedulesList.size() > 0){
            while(schedulesList.size() > 0 && schedulesList.get(0).getEndTime() <= time){
                Schedule firstSchedules = schedulesList.get(0);
                Port port = firstSchedules.getPort();
                Flow flow = firstSchedules.getFlow();
                port.setRemainingBandwidth( port.getRemainingBandwidth() + flow.getBandwidth());
                Schedule scheduleFinished = schedulesList.remove(0);
            }
            time++;
        }

    }


    static  void chooseFlowAndPort(Queue<Flow> optionalFlows, int time,BufferedWriter writer) throws IOException {
        ArrayList<Flow> list = new ArrayList<>();
        while(optionalFlows.size() >0){
            list.add(optionalFlows.poll());
        }
        // 按照最佳适应法选择流和端口
        for (Flow flow : list) {
            Port choosePort = null;
            int minRemainingBandwidth = Integer.MAX_VALUE;
            for (Port port : portList) {
                if(port.getRemainingBandwidth() >= flow.getBandwidth()) {
                    if(port.getRemainingBandwidth() - flow.getBandwidth() < minRemainingBandwidth){
                        minRemainingBandwidth = port.getRemainingBandwidth() - flow.getBandwidth();
                        choosePort = port;
                    }
                }
            }
            if(choosePort != null){
                Schedule schedule = new Schedule(flow, choosePort, time, time + flow.getSpendTime());
                choosePort.setRemainingBandwidth(choosePort.getRemainingBandwidth() - flow.getBandwidth());
                schedulesList.add(schedule);
                Collections.sort(schedulesList, ((o1, o2) -> {return o1.getEndTime() - o2.getEndTime();}));
                writer.write(flow.getId()+","+choosePort.getId()+","+time+"\r\n");
            }else {
                optionalFlows.add(flow);
            }
        }
    }
}
